/**
  ETFAna project, Anyang Normal University && IMP-CAS
  \class ETFDetectorManager
  \brief To manager the creation and registration of detectors according to the users'
  intention. It also by-the-way do the channel-mapping work.
  \author SUN Yazhou, asia.rabbit@163.com
  \since 2021-10-26
  \date 2021-12-03 last modified
  \attention
  changelog
  <table>
  <tr>  <th>Date         <th>Author      <th>Description                   </tr>
  <tr>  <td>2021-10-26   <td>Asia Sun    <td>file created                  </tr>
  </table>

  \copyright Copyright (c) 2021-2024 Anyang Normal U. && IMP-CAS with LGPLv3 LICENSE
*/

#include <iostream>
#include <unistd.h>
#include <regex>
#include <TGraph.h>
#include "ETFDetectorManager.h"
#include "ETFParaManager.h"
#include "ETFRun.h"
#include "ETFYaml.h"
#include "ETFHPTDCCh.h"
#include "ETFMsg.h"
#include "ETFPDCL.h"
#include "ETFPDCR.h"
#include "ETFDC.h"
#include "ETFDCTaU.h"
#include "ETFDCTaD.h"
#include "ETFDCTaL.h"
#include "ETFTimer4.h"
#include "ETFTOFWall.h"
#include "ETFT0Array.h"
#include "ETFTimer2.h"
#include "ETFTimer1.h"
#include "ETFMUSIC.h"

using std::cout;
using std::endl;
using std::regex;

#define er ETFMsg::Error

ETFDetectorManager *ETFDetectorManager::fInstance = nullptr;

ETFDetectorManager::ETFDetectorManager() : fChMap{0}, fTargetZ(-999.),
fDebug(false){}

ETFDetectorManager *ETFDetectorManager::Instance(){
  if(!fInstance) fInstance = new ETFDetectorManager();
  return fInstance;
} // end member function Instance

ETFDetectorManager::~ETFDetectorManager(){
  for(auto &d : fDets) if(d) delete d;
} // end dtor

// register all dets to main tree
void ETFDetectorManager::Register(){
  // branchname, foldername, array address, persistent or transient
  for(auto &d : fDets) d->Register();
} // end member function Register

// create the detectors w.r.t. config file
void ETFDetectorManager::Configure(){
  fpm = ETFParaManager::Instance();
  ETFYaml chIdConfig = *fpm->GetChIdPara();
  ETFYaml detPosConfig = *fpm->GetDetPosPara();
  for(const char *t : {
    // musics
    "mu0", "mu1",
    // tstart2,4: 2-end and 4-end tstart
    // t0Arr: 50 pla arrays placed at F1: 120x2x.5 2-ended SiPM
    "tstart", "tstart2", "tstart2_v", "tstart4", "t0arr",
    // tstop0,1: fan-out signals to VME's 2 mTDCs (v1x90)
    "tstop", "tstop0_v", "tstop1_v", "veto_v",
    "tplate", // tplate: the 4-corner pla plate
    "dcm0", "dcm1", "dcm2", "tofwm", // MWDC Array m - for minus-charge or light frags
    "sipmArr", "sipmBarr", // SiPM coupled Plas - used in 2017 pion exp
    "pdcU0", "pdcU1", "pdcD0", "pdcD1",
    // for general 4-DC pile-up cosmic-ray tests, as usually done by P. Ma //
    "dcTaL0", "dcTaL1", "dcTaL2", "dcTaL3", "tstop4U", "tstop4D",
    // DCTaU,D around the target, made by P.Ma
    // tofwB: the new TOFWall with wider (broader) strips (100mm, with 10mm overlap)
    "dcTaU0", "dcTaU1", "dcTaD0", "dcTaD1",
    "dc0", "dc1", "dc2", "tofwB", "tofw" // MWDC Array - for positive heavy residues
  }){ // construct the detectors accoding to the instruction in the yaml files //
    if(!fpm->Disabled(t) && chIdConfig[t] && detPosConfig[t]){
      if(!strcmp(t, "tstart")) Record(new ETFTimer1(t), t);
      if(regex_match(t, regex("^tstart2(_v){0,1}$"))) Record(new ETFTimer2(t, 100.), t);
      if(!strcmp(t, "tstart4")) Record(new ETFTimer4(t, 100.), t);
      if(!strcmp(t, "t0arr")) Record(new ETFT0Array(t, 50, 2., 120.), t);
      // tracking system downstream the dipole magnet //
      if(regex_match(t, regex("^tstop[01]{0,1}(_v){0,1}$")))
        Record(new ETFTimer2(t, 50.), t);
      if(regex_match(t, regex("veto(_v){0,1}"))) Record(new ETFTimer4(t, 150.), t);
      if(!strcmp(t, "tplate")) Record(new ETFTimer4(t, 1000.), t);
      if(regex_match(t, regex("^dc[m]{0,1}\\d{1}$"))) Record(new ETFDC(t), t);
      if(regex_match(t, regex("^tofw[m]{0,1}$"))) Record(new ETFTOFWall(t), t);
      if(!strcmp(t, "tofwB")) Record(new ETFTOFWall(t, 15, 90.), t);
      // tracking system in target zone //
      if(regex_match(t, regex("^pdcU\\d{1}$"))) Record(new ETFPDCL(t), t);
      if(regex_match(t, regex("^pdcD\\d{1}$"))) Record(new ETFPDCR(t), t);
      if(regex_match(t, regex("^dcTaU\\d{1}$"))) Record(new ETFDCTaU(t), t);
      if(regex_match(t, regex("^dcTaD\\d{1}$"))) Record(new ETFDCTaD(t), t);
      // for miscellaneous DC tests //
      if(regex_match(t, regex("^dcTaL\\d{1}$"))) Record(new ETFDCTaL(t), t);
      if(regex_match(t, regex("^tstop4[UD]{1}$"))) Record(new ETFTimer4(t), t);
      // pion-exp-exclusive detectors //
      if(!strcmp(t, "sipmArr")) Record(new ETFTOFWall(t, 10, 3., 30.), t);
      if(!strcmp(t, "sipmBarr")) Record(new ETFTOFWall(t, 24, 20., 300.), t);
      // musics
      if(regex_match(t, regex("^mu\\d{1}$"))) Record(new ETFMUSIC(t), t);
    } // end outer if
    // else er("ETFDetectorManager", "Configure: det %s chid or detPos not assigned", t);
  } // end for over the DCs

  if(detPosConfig["target"])
    fTargetZ = detPosConfig["target"]["pos"][2].as<double>();
} // end member function Configure

// Record det in map fDetMap and fDets
void ETFDetectorManager::Record(ETFDetector *det, const char *name){
  if(fDetMap.count(name))
    er("ETFDetectorManager", "Record: det %s already recorded.", name);
  fDets.push_back(det);
  fDetMap[name] = det;
  det->SetName(name);
} // end member function Record

ETFDetector *ETFDetectorManager::GetDetector(const string &name, bool warn) const{
  if(!fDetMap.count(name)){
    if(warn) er("ETFDetectorManager", "GetDetector: detector %s not found", name.c_str());
    return nullptr;
  } // end if
  return fDetMap.at(name);
} // end member function GetDetector

ETFDetector *ETFDetectorManager::GetDetector(int detId, bool warn) const{
  if(detId > int(fDets.size()))
    er("ETFDetectorManager", "GetDetector: unregistered det Id: %d", detId);
  detId--;
  if(warn && !fDets[detId])
    er("ETFDetectorManager", "GetDetector: detector id %d not found", detId);
  return fDets[detId];
} // end member function GetDetector

// identify ch as a hit and add to a specific detector
void ETFDetectorManager::Assign(ETFRawChannel *ch){
  const unsigned uid = GetUID(ch->chid());
  if(!uid) return; // unregistered
  ETFDetector *det = GetDetector(uid & 0xFF);
  if(!det) return;

  if(ch->IsT()){
    auto tch = static_cast<ETFHPTDCCh *>(ch);
    // tch->SelfCheck(); // make the only trailing edge a leading one
    if(!tch->nl()) return; // no leading edges, invalid channel
    if(!det->TrigBoundCheck(tch)) return;
  } // end if
  det->Assign(ch, uid);
  if(fDebug){ ch->Print(); getchar(); }
} // end member function Assign

// finish the processing of all the assigned data
void ETFDetectorManager::PostAssign(){
  for(auto &d : fDets) d->PostAssign();
} // end member function PostAssign

void ETFDetectorManager::SetChMap(int chid, unsigned uid){
  if(chid < 0 || chid >= CH_MAX) er("ETFDetectorManager", "GetUID: chid %d out of range", chid);
  fChMap[chid] = uid;
} // end member function SetChMap

unsigned ETFDetectorManager::GetUID(int chid) const{
  if(chid < 0 || chid >= CH_MAX) er("ETFDetectorManager", "GetUID: chid %d out of range", chid);
  return fChMap[chid];
} // end member function GetUID

void ETFDetectorManager::Reset(){
  fEventIndex = -1;
  for(auto &d : fDets) d->Reset();
} // end member function Reset

void ETFDetectorManager::Draw(){
  ConfigureDraw();
  fGMagnet->Draw("plsame");
  fGBeamLine->Draw("plsame");
  fGTarget->Draw("plsame");
  for(auto &d : fDets) d->Draw();
} // end member function Draw

// draw the dipole magnet and the neutral beamline
void ETFDetectorManager::ConfigureDraw(){
  // show the magnet
  fGMagnet = new TGraph();
	fGMagnet->SetMarkerColor(5); fGMagnet->SetMarkerStyle(29);
	fGMagnet->SetLineWidth(3); fGMagnet->SetLineColor(3);
	fGMagnet->SetPoint(0, 475., 500.); fGMagnet->SetPoint(1, 475., -500.);
	fGMagnet->SetPoint(2, -475., -500.); fGMagnet->SetPoint(3, -475., 500.);
	fGMagnet->SetPoint(4, 475., 500.);
  // show the beamline
  fGBeamLine = new TGraph();
	fGBeamLine->SetLineColor(8); fGBeamLine->SetLineStyle(3); fGBeamLine->SetLineWidth(2);
	fGBeamLine->SetPoint(0, -10000., 0.); fGBeamLine->SetPoint(1, 15000., 0.);
  // show the target
  fGTarget = new TGraph();
  fGTarget->SetMarkerStyle(29); fGTarget->SetMarkerColor(kRed);
  fGTarget->SetMarkerSize(2);
  fGTarget->SetPoint(0, fTargetZ, 0.);
} // end member function ConfigureDraw

void ETFDetectorManager::Print(){
  cout << "\033[33;1m########## DETECTOR MANAGER PRINT ##############\033[0m" << endl;
  cout << "Event " << fEventIndex << endl;
  for(auto &d : fDets) d->Print();
} // end member function Reset
